package net.lovesrc.allin.biz.cpct;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.zhouzhou.fastjava.BizException;
import com.zhouzhou.fastjava.http.FilePart;
import com.zhouzhou.fastjava.http.MyHttpRequest;
import com.zhouzhou.fastjava.http.Requests;
import com.zhouzhou.fastjava.util.BigDecimalKit;
import com.zhouzhou.fastjava.util.FileKit;
import com.zhouzhou.fastjava.util.StringKit;
import com.zhouzhou.fastjava.util.ThreadKit;
import net.lovesrc.allin.ConfigKeys;
import net.lovesrc.allin.biz.BaseService;
import net.lovesrc.allin.biz.idcard.IdCardService;
import net.lovesrc.support.service.mobileverifycode.MobileVerifyCodeService;
import net.lovesrc.support.service.mobileverifycode.MobileVerifyCodeWrapper;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.BasicCookieStore;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;

@Service
public class CPCTService extends BaseService {

    private static final String VERIFY_CODE_ITEM_ID = "26680";

    @Autowired
    private MobileVerifyCodeService mobileVerifyCodeService;

    private MobileVerifyCodeWrapper getMobileVerifyCodeWrapper() {
        return new MobileVerifyCodeWrapper(mobileVerifyCodeService, VERIFY_CODE_ITEM_ID);
    }

    public String newRegister(String registerUrl) {
        // https://www.cpct.pro/regcode/ZCbg2b7B3D
        String promotionCode = StringKit.extract(registerUrl, "\\/regcode\\/([a-zA-Z0-9]+)");
        return this.newRegisterByPromotionCode(promotionCode);
    }

    public String newRegisterByPromotionCode(String promotionCode) {
        JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_account where promotionCode = ?", promotionCode);
        if (row == null) {
            throw BizException.messageOf("{} 邀请码，在系统中不存在", promotionCode);
        }
        String registerUrl = "https://www.cpct.pro/regcode/" + promotionCode;
        BasicCookieStore cookieStore = new BasicCookieStore();
        HttpClient httpClient = newHttpClientWithProxy(null, cookieStore);
        MyHttpRequest.get(httpClient, registerUrl)
                .execute()
                .asText();

        // 获取一个手机号
        String newMobile = doGetNewMobile();

        // 发送验证码
        try {
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/sms/registSmsCode")
                    .param("country", "+86")
                    .param("username", newMobile)
                    .header("referer", registerUrl)
                    .execute()
                    .asJson();
            checkResJson(resJson);
        } catch (Exception e) {
            mobileVerifyCodeService.releasePhone(VERIFY_CODE_ITEM_ID, newMobile);
            throw e;
        }

        // 获取验证码
        String verifyCode = doGetVerifyCode(newMobile);

        String newPassword = StringUtils.right(newMobile, 4) + "abcd";
        // 注册
        String password = md5(newPassword);

        {
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/registServiceMobile")
                    .param("mobile", newMobile)
                    .param("password", password)
                    .param("registSmsCode", verifyCode)
                    .param("country", "+86")
                    .param("referralCode", promotionCode)
                    .header("referer", registerUrl)
                    .executeUtilReturn200()
                    .asJson();
            boolean success = resJson.getBooleanValue("success");
            if (success) {
                // is ok
            } else {
                String msg = resJson.getString("msg");
                if (msg.contains("已注册")) {
                    JSONObject meJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/sencodvail")
                            .param("type", "1")
                            .param("username", newMobile)
                            .param("password", password)
                            .param("country", "+86")
                            .executeUtilReturn200()
                            .asJson();
                    if (meJson.getBoolean("success")) {
                        // is ok
                    } else {
                        mobileVerifyCodeService.addBlack(VERIFY_CODE_ITEM_ID, newMobile);
                        throw BizException.messageOf(msg);
                    }
                } else {
                    throw BizException.messageOf(msg);
                }
            }
        }
        // 保存账户信息
        {
            JSONObject newRow = new JSONObject()
                    .fluentPut("mobile", newMobile)
                    .fluentPut("password", newPassword)
                    .fluentPut("promotionCode", promotionCode)
                    .fluentPut("idStatus", "未提交");
            insertRow(jdbcTemplate, "t_cpct_sub_account", newRow);
        }
        return newMobile;
    }

    private List<String> authIdRunningMobiles = Lists.newCopyOnWriteArrayList();

    public void idIdentity(String mobile) {

        Integer maxIngs = tryGetProperties(int.class, 1, "CPCT_AuthId_MAX_INGS");
        if (authIdRunningMobiles.size() >= maxIngs) {
            return;
        }
        if (authIdRunningMobiles.contains(mobile)) {
            return;
        }
        // 获取账号信息
        JSONObject subAccount = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", mobile);
        if (subAccount == null) {
            throw BizException.messageOf("无{}此账号", mobile);
        }
        if (StringUtils.isNotBlank(subAccount.getString("idCardNumber"))) {
            throw BizException.messageOf("此账号已做身份认证");
        }
        Date insertTime = subAccount.getDate("insertTime");
        int minSeconds = tryGetProperties(int.class, 180, ConfigKeys.CPCT_AUTH_ID_MIN_SECONDS);
        if (insertTime.after(DateUtils.addSeconds(new Date(), 0 - minSeconds))) {
            throw BizException.messageOf("注册成功{}秒后，才能做实名认证", minSeconds);
        }
        if (!"未提交".equalsIgnoreCase(subAccount.getString("idStatus"))) {
            throw BizException.messageOf("未提交的才能提交实名认证");
        }
        authIdRunningMobiles.add(mobile);
        execute(() -> {
            try {
                doAutId(mobile, subAccount);
            } finally {
                authIdRunningMobiles.remove(mobile);
            }
        });
    }

    private void doAutId(String mobile, JSONObject subAccount) {
        jdbcTemplate.update("update t_cpct_sub_account set idStatus = ? where mobile = ?", "正在提交实名", mobile);
        try {
            CookieStore cookieStore = Requests.toCookieStore(subAccount.getString("cookies"));
            HttpClient httpClient = ensureLogin(cookieStore, subAccount);
            // 个人首页
            MyHttpRequest.get(httpClient, "https://www.cpct.pro/user/center")
                    .execute()
                    .asText();
            // 取一个没有使用的ID
            JSONObject idCardRow = queryForRow(jdbcTemplate, "select * from t_id_card where worked = 1 and " +
                    " idCardNumber not in (select idCardNumber from t_cpct_sub_account where idCardNumber is not null) " +
                    " and idCardNumber not in (select idCardNumber from t_cpct_used_id) " +
                    " order by rand() limit 1");
            if (idCardRow == null) {
                throw BizException.messageOf("无可用的身份证信息");
            }
            String idCardNumber = idCardRow.getString("idCardNumber");
            String realName = idCardRow.getString("realName");
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/identity")
                    .param("country", "中国大陆")
                    .param("type", "1")
                    .param("cardType", "0")
                    .param("surname", realName.substring(0, 1))
                    .param("trueName", realName.substring(1))
                    .param("cardId", idCardNumber)
                    .part(getIdFile(idCardNumber, "1"))
                    .part(getIdFile(idCardNumber, "2"))
                    .part(getIdFile(idCardNumber, "3"))
                    .execute()
                    .asJson();
            jdbcTemplate.update("insert into t_cpct_used_id(idCardNumber, mobile, resCode, resMessage) values(?, ?, ?, ?)", idCardNumber, mobile, resJson.getIntValue("code"), resJson.getString("msg"));
            checkResJson(resJson);
            jdbcTemplate.update("update t_cpct_sub_account set idCardNumber = ?, idTime = ?, idStatus = ? where mobile = ?", idCardNumber, new Date(), "审核中", mobile);
        } catch (Exception e) {
            logger.error("提交实名失败", e);
            jdbcTemplate.update("update t_cpct_sub_account set idStatus = ?, idErrorMessage = ? where mobile = ?", "提交实名失败", e.getMessage(), mobile);
        }
    }

    public String syncUserMoney(HttpClient httpClient, String mobile) {
        String html = MyHttpRequest.get(httpClient, "https://www.cpct.pro/user/index")
                .param("_", System.currentTimeMillis())
                .printResText(false)
                .executeUtilReturnNormal()
                .asText();
        if (StringUtils.isNotBlank(html)) {
            Document root = Jsoup.parse(html);
            Elements trs = root.select("div.cptcmtabel tr.ng-scope");
            BigDecimal cpctQty = BigDecimal.valueOf(-1);
            BigDecimal ethQty = BigDecimal.valueOf(-1);
            for (Element tr : trs) {
                Element bz = tr.selectFirst("span.bizhong_en");
                String bzEn = bz.text().trim();
                if ("CPCT".equalsIgnoreCase(bzEn)) {
                    String qtyString = tr.selectFirst("span.rate_up").text();
                    cpctQty = BigDecimal.valueOf(Double.valueOf(qtyString));
                    continue;
                }
                if ("ETH".equalsIgnoreCase(bzEn)) {
                    String qtyString = tr.selectFirst("span.rate_up").text();
                    ethQty = BigDecimal.valueOf(Double.valueOf(qtyString));
                    continue;
                }
            }
            jdbcTemplate.update("update t_cpct_sub_account set cpctQty = ?, ethQty = ? where mobile = ?", cpctQty, ethQty, mobile);
        }
        return html;
    }

    public HttpClient ensureLogin(CookieStore cookieStore, JSONObject subAccount) {

        String mobile = subAccount.getString("mobile");
        HttpClient httpClient = newHttpClientBuilder(cookieStore).build();
        String html = this.syncUserMoney(httpClient, mobile);
        if (StringUtils.isNotBlank(html)) {
            // ok
        } else {
            doLogin(httpClient, subAccount);
            this.syncUserMoney(httpClient, mobile);
        }
        jdbcTemplate.update("update t_cpct_sub_account set cookies = ? where mobile = ?", Requests.toCookieString(cookieStore), mobile);
        return httpClient;
    }

    /**
     * NEW
     * BUY_IN
     * SELL_OUT
     * TRANSFER_OUT
     */
    private void doLogin(HttpClient httpClient, JSONObject subAccount) {
        MyHttpRequest.get(httpClient, "https://www.cpct.pro")
                .executeUtilReturnNormal()
                .asText();
        String mobile = subAccount.getString("mobile");
        String password = md5(subAccount.getString("password"));
        {
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/sencodvail")
                    .param("type", "1")
                    .param("username", mobile)
                    .param("password", password)
                    .param("country", "+86")
                    .executeUtilReturn200()
                    .asJson();
            checkResJson(resJson);
        }
        {
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/loginService")
                    .param("username", mobile)
                    .param("password", password)
                    .param("country", "+86")
                    .executeUtilReturn200()
                    .asJson();
            checkResJson(resJson);
        }
    }

    private FilePart getIdFile(String idCardNumber, String filePrefix) {
        File dir = new File(IdCardService.ROOT, idCardNumber);
        File file = FileKit.tryGetFile(dir, filePrefix, IdCardService.ACCEPT_EXTENSIONS);
        if (!file.exists()) {
            throw BizException.messageOf("{} 文件不存在", file);
        }
        String extension = FilenameUtils.getExtension(file.getName());
        String filename = RandomStringUtils.randomAlphanumeric(5) + "_" + (DateUtils.addMinutes(new Date(), -10).getTime()) + "." + extension;
        return FilePart.builder()
                .name("file")
                .filename(filename)
                .contentType(FileKit.getConentType(filename))
                .file(file)
                .build();
    }

    private String doGetVerifyCode(String newMobile) {
        return mobileVerifyCodeService.getVerifyCodeFromPhone(VERIFY_CODE_ITEM_ID, newMobile);
    }

    private String doGetNewMobile() {
        for (int i = 0; i < 50; i++) {
            String mobile = mobileVerifyCodeService.getPhone(VERIFY_CODE_ITEM_ID);
            int count = jdbcTemplate.queryForObject("select count(1) from t_cpct_sub_account where mobile = ?", int.class, mobile);
            if (count == 0) {
                return mobile;
            }
            mobileVerifyCodeService.addBlack(VERIFY_CODE_ITEM_ID, mobile);
        }
        throw BizException.messageOf("未能获取到新的手机号");
    }

    public void save(String registerUrl, String mobile, String note) {
        checkNotBlank("registerUrl", registerUrl);
        checkNotBlank("note", note);
        String promotionCode = StringKit.extract(registerUrl, "\\/regcode\\/([a-zA-Z0-9]+)");
        JSONObject dbRow = queryForRow(jdbcTemplate, "select * from t_cpct_account where promotionCode = ?", promotionCode);
        if (dbRow == null) {
            JSONObject newRow = new JSONObject()
                    .fluentPut("promotionCode", promotionCode)
                    .fluentPut("registerUrl", registerUrl)
                    .fluentPut("mobile", mobile)
                    .fluentPut("note", note);
            insertRow(jdbcTemplate, "t_cpct_account", newRow);
        } else {
            jdbcTemplate.update("update t_cpct_account set mobile = ?, note = ? where promotionCode = ?", mobile, note, promotionCode);
        }

    }

    public void fetchLatestIdAuditStatus() {
        if (tryGetProperties(boolean.class, false, "CPCT_Fetch_Id_Audit_status")) {
            List<JSONObject> rows = queryForRows(jdbcTemplate, "select * from t_cpct_sub_account where idStatus = ?", "审核中");
            for (JSONObject row : rows) {
                String mobile = row.getString("mobile");
                try {
                    doFetchIdAuditStatus(mobile, row);
                } catch (Exception e) {
                    jdbcTemplate.update("update t_cpct_sub_account set idStatus = ?, idErrorMessage = ? where mobile = ?", "实名失败", e.getMessage(), mobile);
                }
            }
        }
    }

    private void doFetchIdAuditStatus(String mobile, JSONObject row) {
        String cookies = row.getString("cookies");
        HttpClient httpClient = ensureLogin(Requests.toCookieStore(cookies), row);
        String html = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/realinfo")
                .execute()
                .asText();
        String idCardNumber = row.getString("idCardNumber");
        if (html.contains("实名认证正在审核，请耐心等待")) {
            // 审核中
        } else if (html.contains("您已通过交易所实名认证")) {
            jdbcTemplate.update("update t_cpct_sub_account set idStatus = ?, idSuccessTime = ? where mobile = ?", "实名成功", new Date(), mobile);
            jdbcTemplate.update("update t_id_card set workedTimes = workedTimes + 1 where idCardNumber = ?", idCardNumber);
        } else {
            Document root = Jsoup.parse(html);
            String message = root.selectFirst(".two-rz-title .text-success").text();
            jdbcTemplate.update("insert into t_cpct_used_id(idCardNumber, mobile, resCode, resMessage) values(?, ?, ?, ?)", idCardNumber, mobile, -1, message);
            throw BizException.messageOf(message);
        }
    }

    private void onlyCheckResJson(JSONObject resJson) {
        boolean success = resJson.getBooleanValue("success");
        if (!success) {
            throw BizException.messageOf(resJson.getString("msg"));
        }
    }

    private JSONObject checkResJson(JSONObject resJson) {
        boolean success = resJson.getBooleanValue("success");
        if (!success) {
            throw BizException.messageOf(resJson.getString("msg"));
        }
        return resJson.getJSONObject("obj");
    }

    public void autoSubmitAuthId() {
        if (disableAutoSubmitAuthId()) {
            return;
        }
        List<JSONObject> rows = queryForRows(jdbcTemplate, "select * from t_cpct_sub_account where idStatus = ? order by rand() limit 5", "未提交");
        for (JSONObject row : rows) {
            if (disableAutoSubmitAuthId()) {
                return;
            }
            try {
                this.idIdentity(row.getString("mobile"));
            } catch (Exception e) {
                // ignore
            }
        }
    }

    private boolean disableAutoSubmitAuthId() {
        return !tryGetProperties(boolean.class, false, "Auto_Submit_CPCT_Auth_Id");
    }

    public void buyCpct(HttpClient httpClient, String mobile, BigDecimal price, BigDecimal cpctQty) {
        this.syncUserMoney(httpClient, mobile);
        JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", mobile);
        if (row.getBigDecimal("cpctQty").intValue() < 6) {
            throw BizException.messageOf("{} 中没有6个CPCT币", mobile);
        }
        JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/trades/add")
                .param("entrustPrice", price)
                .param("entrustCount", cpctQty)
                .param("entrustSum", cpctQty)
                .param("source", 1)
                .param("coinCode", "CPCT_ETH")
                .param("type", 1)
                .param("entrustWay", 1)
                .execute()
                .asJson();
        checkResJson(resJson);
        jdbcTemplate.update("update t_cpct_sub_account set status = 'BUY_IN' where mobile = ?", mobile);
    }

    private CpctPrice calcPrice(HttpClient httpClient) {
        JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/getAccountInfo")
                .param("coinCode", "CPCT_ETH")
                .execute()
                .asJson();
        onlyCheckResJson(resJson);
        String obj = resJson.getString("obj");
        JSONObject objJson = JSON.parseObject(obj);
        double sellOnePrice = objJson.getDoubleValue("sellOnePrice");
        double buyOnePrice = objJson.getDoubleValue("buyOnePrice");
        double diffPrice = (sellOnePrice - buyOnePrice) / 5;
        BigDecimal buyerPrice = BigDecimal.valueOf(buyOnePrice + diffPrice).setScale(6, RoundingMode.DOWN);
        BigDecimal sellerPrice = BigDecimal.valueOf(sellOnePrice - diffPrice).setScale(6, RoundingMode.DOWN);
        if (BigDecimalKit.gt(buyerPrice, sellerPrice)) {
            throw BizException.messageOf("价格有误");
        }
        return CpctPrice.builder()
                .buyerPrice(buyerPrice)
                .buyOnePrice(BigDecimalKit.of(buyOnePrice))
                .sellerPrice(sellerPrice)
                .sellOnePrice(BigDecimalKit.of(sellOnePrice))
                .build();
    }

    public void sellCpct(HttpClient httpClient, String mobile, BigDecimal price, BigDecimal cpctQty) {
        JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/trades/add")
                .param("entrustPrice", price)
                .param("entrustCount", cpctQty)
                .param("entrustSum", cpctQty)
                .param("source", 1)
                .param("coinCode", "CPCT_ETH")
                .param("type", 2)
                .param("entrustWay", 1)
                .execute()
                .asJson();
        checkResJson(resJson);
        jdbcTemplate.update("update t_cpct_sub_account set status = 'SELL_OUT' where mobile = ?", mobile);
    }

    public void setOthersProps(String mobile) {
        JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", mobile);
        String cookies = row.getString("cookies");
        CookieStore cookieStore = Requests.toCookieStore(cookies);
        HttpClient httpClient = ensureLogin(cookieStore, row);
        this.setOthersProps(httpClient, mobile);
    }

    public void setOthersProps(HttpClient httpClient, String mobile) {
        JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", mobile);
        if (StringUtils.isNotBlank(row.getString("ethPublicKey"))
                && StringUtils.isNotBlank(row.getString("payPass"))) {
            return;
        }
        if (StringUtils.isBlank(row.getString("payPass"))) {
            String payPass = "abcd" + StringUtils.right(mobile, 4);
            String accountPassWord = md5(payPass);
            getMobileVerifyCodeWrapper().getPhone(mobile);
            try {
                MyHttpRequest.post(httpClient, "https://www.cpct.pro/sms/getReApwSmsCode")
                        .param("accountPassWord", accountPassWord)
                        .param("reaccountPassWord", accountPassWord)
                        .execute()
                        .asJson();
            } catch (Exception e) {
                logger.error("Error ", e);
                getMobileVerifyCodeWrapper().releasePhone(mobile);
                throw e;
            }
            String verifyCode = getMobileVerifyCodeWrapper().getVerifyCodeFromPhone(mobile);
            MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/resetapwsubmit")
                    .param("accountPassWord", accountPassWord)
                    .param("reaccountPassWord", accountPassWord)
                    .param("accountpwSmsCode", verifyCode)
                    .execute()
                    .asJson();
            jdbcTemplate.update("update t_cpct_sub_account set payPass = ? where mobile = ?", payPass, mobile);
        }
        if (StringUtils.isBlank(row.getString("ethPublicKey"))) {
            String html = MyHttpRequest.get(httpClient, "https://www.cpct.pro/user/btc/post")
                    .param("_", System.currentTimeMillis())
                    .printResText(false)
                    .execute()
                    .asText();
            Document root = Jsoup.parse(html);
            Element ethButton = root.select("button[name=coinobjcet]")
                    .stream()
                    .filter(r -> r.text().equalsIgnoreCase("ETH"))
                    .findFirst()
                    .get();
            String accountid = ethButton.attr("accountid");
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/btc/getPublicKey")
                    .param("accountId", accountid)
                    .execute()
                    .asJson();
            JSONObject resObj = resJson.getJSONObject("obj");
            String ethPublicKey = resObj.getString("publicKey");
            if (StringUtils.isBlank(ethPublicKey)) {
                resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/btc/createPublicKey")
                        .param("accountId", accountid)
                        .execute()
                        .asJson();
                ethPublicKey = resJson.getJSONObject("obj").getString("publicKey");
            }
            jdbcTemplate.update("update t_cpct_sub_account set ethPublicKey = ? where mobile = ?", ethPublicKey, mobile);
        }
    }

    private List<JSONObject> myAccounts = Lists.newArrayList(
            new JSONObject()
                    .fluentPut("mobile", "zb_15921083643")
                    .fluentPut("eth", "0xb465277720f70e07c9aca4f3b32b35c1135e7427")
    );

    private void doTransferEth(String fromMobile, String toMobile, BigDecimal ethQty) {

        boolean toMyAccount = myAccounts.stream()
                .anyMatch(r -> r.getString("mobile").equalsIgnoreCase(toMobile));
        if (!toMyAccount) {
            getMobileVerifyCodeWrapper().getPhone(toMobile);
            getMobileVerifyCodeWrapper().releasePhone(toMobile);
        }
        double cpctQty = jdbcTemplate.queryForObject("select cpctQty from t_cpct_sub_account where mobile = ?", double.class, fromMobile);
        if (cpctQty > 0.0001) {
            throw BizException.messageOf("还有Cpct没有卖呢");
        }
        JSONObject fromRow = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", fromMobile);
        String fromEthPublicKey = fromRow.getString("ethPublicKey");

        String toEthPublicKey;
        if (toMyAccount) {
            toEthPublicKey = myAccounts.stream()
                    .filter(r -> r.getString("mobile").equalsIgnoreCase(toMobile))
                    .findFirst()
                    .get()
                    .getString("eth");
        } else {
            JSONObject toRow = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", toMobile);
            toEthPublicKey = toRow.getString("ethPublicKey");

            if (StringUtils.isBlank(toEthPublicKey)) {
                this.setOthersProps(toMobile);
                toRow = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", toMobile);
                toEthPublicKey = toRow.getString("ethPublicKey");
                if (StringUtils.isBlank(toEthPublicKey)) {
                    throw BizException.messageOf("转出账号异常");
                }
            }
        }

        String cookies = fromRow.getString("cookies");
        CookieStore cookieStore = Requests.toCookieStore(cookies);
        HttpClient httpClient = ensureLogin(cookieStore, fromRow);
        this.setOthersProps(httpClient, fromMobile);

        doTransfer(httpClient, fromMobile, fromEthPublicKey, toMobile, toEthPublicKey, ethQty);
    }

    private void doTransfer(HttpClient httpClient, String fromMobile, String fromEthPublicKey, String toMobile, String toEthPublicKey, BigDecimal ethQty) {

        if (StringUtils.isBlank(fromEthPublicKey)
                || StringUtils.isBlank(toEthPublicKey)
                || !fromEthPublicKey.startsWith("0x")
                || !toEthPublicKey.startsWith("0x")) {
            throw BizException.messageOf("数据异常，无法转出");
        }
        // add address
        MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/publickeylist/save")
                .param("currencyType", "ETH")
                .param("publicKey", toEthPublicKey)
                .param("remark", StringUtils.EMPTY)
                .execute()
                .asJson();

        String payPass = jdbcTemplate.queryForObject("select payPass from t_cpct_sub_account where mobile = ?", String.class, fromMobile);

        MyHttpRequest.post(httpClient, "https://www.cpct.pro/sencodvail")
                .param("coinType", "ETH")
                .param("currencyType", "USD")
                .param("btcNum", ethQty)
                .param("btcKey", toEthPublicKey)
                .param("type", 3)
                .param("remark", StringUtils.EMPTY)
                .param("accountPw", md5(payPass))
                .execute()
                .asJson();

        {
            JSONObject newRow = new JSONObject()
                    .fluentPut("fromMobile", fromMobile)
                    .fluentPut("fromEthPublicKey", fromEthPublicKey)
                    .fluentPut("toMobile", toMobile)
                    .fluentPut("toEthPublicKey", toEthPublicKey)
                    .fluentPut("ethQty", ethQty)
                    .fluentPut("status", "Start_Transfer");
            insertRow(jdbcTemplate, "t_cpct_transfer", newRow);
        }

        JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/publickeylist/getOrder")
                .param("coinType", "ETH")
                .param("accountPw", md5(payPass))
                .param("currencyType", "USD")
                .param("btcNum", ethQty)
                .param("btcKey", toEthPublicKey)
                .param("shouxufei", "0.00500000")
                .execute()
                .asJson();
        checkResJson(resJson);

        JSONObject transferOrder = getTransferOrders(httpClient).get(0);
        String orderNum = transferOrder.getString("transactionNum");
        jdbcTemplate.update("update t_cpct_transfer set status = 'Transfer_Success', orderNum = ? where fromMobile = ? and toMobile = ?", orderNum, fromMobile, toMobile);
        notificationService.syncEmail(String.format("转出成功，fromMobile: %s，toMobile: %s, Qty: %s", fromMobile, toMobile, ethQty));
    }

    public void clearTransferPublicAddress(HttpClient httpClient) {
        String html = MyHttpRequest.get(httpClient, "https://www.cpct.pro/user/publickeylist/index")
                .param("_", System.currentTimeMillis())
                .printResText(false)
                .executeUtilReturn200()
                .asText();
        Document doc = Jsoup.parse(html);
        Elements trs = doc.select("table#tablepublic tbody tr");
        for (Element tr : trs) {
            System.out.println(tr.html());
            String id = tr.selectFirst("#deletePubValue").attr("value");
            MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/publickeylist/delete")
                    .param("id", id)
                    .execute()
                    .asText();
        }
    }

    public List<JSONObject> getTransferOrders(HttpClient httpClient) {
        JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.cpct.pro/user/publickeylist/list")
                .param("limit", 10)
                .param("offset", 0)
                .param("sortOrder", "asc")
                .param("type", StringUtils.EMPTY)
                .param("status", StringUtils.EMPTY)
                .executeUtilReturn200()
                .asJson();
        JSONArray rows = resJson.getJSONArray("rows");
        if (rows == null || rows.isEmpty()) {
            return Lists.newArrayList();
        }
        List<JSONObject> result = Lists.newArrayList();
        for (int i = 0; i < rows.size(); i++) {
            result.add(rows.getJSONObject(i));
        }
        return result;
    }

    private String md5(String str) {
        return StringKit.md5(str + "hello, moto");
    }

    public void transferEth() {

        List<JSONObject> nextRows = queryForRows(jdbcTemplate, "select * from t_cpct_transfer where status = 'Transfer_Complete' and nextStatus = 'No_Start' and length(toMobile) = 11 order by id");
        for (JSONObject nextRow : nextRows) {
            String nextMobile = nextRow.getString("toMobile");
            if (this.doTransferEth(nextMobile)) {
                if (jdbcTemplate.queryForObject("select count(1) from t_cpct_transfer where fromMobile = ?", int.class, nextMobile) > 0) {
                    jdbcTemplate.update("update t_cpct_transfer set nextStatus = 'Success' where id = ?", nextRow.getLong("id"));
                } else {
                    jdbcTemplate.update("update t_cpct_transfer set nextStatus = 'Failure', errorMsg = ? where id = ?", "无下一行转账记录", nextRow.getLong("id"));
                }
            } else {
                break;
            }
        }
    }

    private boolean doTransferEth(String mobile) {
        JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", mobile);
        String status = row.getString("status");
        if ("NEW".equalsIgnoreCase(status)
                || "SELL_OUT".equalsIgnoreCase(status)) {
            // is okay
        } else {
            throw BizException.messageOf("状态异常");
        }

        String cookies = row.getString("cookies");
        CookieStore cookieStore = Requests.toCookieStore(cookies);
        HttpClient httpClient = ensureLogin(cookieStore, row);
        CpctPrice cpctPrice = calcPrice(httpClient);
        if (cpctPrice.getSellerPrice().doubleValue() - cpctPrice.getBuyOnePrice().doubleValue() > 0.0001) {
            return false;
        }
        this.syncUserMoney(httpClient, mobile);

        if ("NEW".equalsIgnoreCase(status)) {
            double cpctQty = jdbcTemplate.queryForObject("select cpctQty from t_cpct_sub_account where mobile = ?", double.class, mobile);
            double beforeBuyEthQty = jdbcTemplate.queryForObject("select ethQty from t_cpct_sub_account where mobile = ?", double.class, mobile);
            if (cpctQty < 30) {
                this.buyCpct(httpClient, mobile, cpctPrice.getSellOnePrice(), BigDecimalKit.of(30));
                ThreadKit.sleepSeconds(2);
                this.syncUserMoney(httpClient, mobile);
            }
            double newCpctQty = jdbcTemplate.queryForObject("select cpctQty from t_cpct_sub_account where mobile = ?", double.class, mobile);
            if (newCpctQty < 30) {
                throw BizException.messageOf("CPCT 数量小于30");
            }
            this.sellCpct(httpClient, mobile, cpctPrice.getBuyOnePrice(), BigDecimalKit.of(newCpctQty));
            ThreadKit.sleepSeconds(3);
            this.syncUserMoney(httpClient, mobile);
            double afterSellEthQty = jdbcTemplate.queryForObject("select ethQty from t_cpct_sub_account where mobile = ?", double.class, mobile);
            double afterSellCpctQty = jdbcTemplate.queryForObject("select cpctQty from t_cpct_sub_account where mobile = ?", double.class, mobile);
            if ((afterSellEthQty - beforeBuyEthQty <= 0) || afterSellCpctQty > 0.0001) {
                notificationService.syncEmail("ETH，数量没有变化, 请检查");
                throw BizException.messageOf("ETH，数量没有变化");
            }
        }
        final double ETH_PER_ONE = 0.11;
        ThreadKit.sleepSeconds(5);
        double ethQty = getAvailbleEthQty(httpClient);
        int n = Double.valueOf(ethQty / ETH_PER_ONE).intValue();
        if (n <= 0) {
            throw BizException.messageOf("ETH 数量不对");
        }

        final boolean onlyTransferToMy = tryGetProperties(boolean.class, false, "CPCT_Only_Transfer_To_My");
        final boolean transferToMyself = true;
        if (onlyTransferToMy) {
            BigDecimal othersEthQty = BigDecimal.valueOf(ethQty)
                    .setScale(7, RoundingMode.DOWN);
            JSONObject myAccount = myAccounts.get(RandomUtils.nextInt(0, myAccounts.size()));
            this.doTransferEth(mobile, myAccount.getString("mobile"), othersEthQty);
        } else if (transferToMyself && n >= 2) {
            JSONObject toRow = doGetCandidateToRow();
            String toMobile = toRow.getString("mobile");
            BigDecimal ethQtyOne = BigDecimal.valueOf(ETH_PER_ONE);
            this.doTransferEth(mobile, toMobile, ethQtyOne);
            BigDecimal othersEthQty = BigDecimal.valueOf(ethQty - ethQtyOne.doubleValue())
                    .setScale(7, RoundingMode.DOWN);
            JSONObject myAccount = myAccounts.get(RandomUtils.nextInt(0, myAccounts.size()));
            this.doTransferEth(mobile, myAccount.getString("mobile"), othersEthQty);
        } else {
            double used = 0;
            for (int i = 0; i < n; i++) {
                JSONObject toRow = doGetCandidateToRow();
                String toMobile = toRow.getString("mobile");
                BigDecimal ethQtyOne;
                if (i == n - 1) {
                    ethQtyOne = BigDecimal.valueOf(ethQty - used)
                            .setScale(7, RoundingMode.DOWN);
                } else {
                    ethQtyOne = BigDecimal.valueOf(ethQty / n)
                            .setScale(7, RoundingMode.DOWN);
                }
                this.doTransferEth(mobile, toMobile, ethQtyOne);
                used += ethQtyOne.doubleValue();
            }
        }
        return true;
    }

    private double getAvailbleEthQty(HttpClient httpClient) {

        String html = MyHttpRequest.get(httpClient, "https://www.cpct.pro/user/btc/get")
                .param("_", System.currentTimeMillis())
                .printResText(false)
                .executeUtilReturn200()
                .asText();
        Document doc = Jsoup.parse(html);
        Elements options = doc.select("select#coinSelect option");
        Element ethOption = options.stream()
                .filter(r -> r.text().trim().equalsIgnoreCase("ETH"))
                .findFirst()
                .get();
        String value = ethOption.attr("value");
        String[] strings = value.split("HURONGSPLIT");
        return Double.valueOf(strings[1]);
    }

    public void resetNewOnePayPass() {
        for (; ; ) {
            JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where status = 'NEW' and payPass is null order by rand() limit 1");
            if (row == null) {
                break;
            }
            if (isWorkedRow(row)) {
                return;
            }
        }
    }

    private JSONObject doGetCandidateToRow() {
        for (; ; ) {
            JSONObject row = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where status = 'NEW' and payPass is not null order by cpctQty desc limit 1");
            if (row == null) {
                break;
            }
            if (isWorkedRow(row)) {
                return row;
            }
        }
        throw BizException.messageOf("无可用小号了");
    }

    private boolean isWorkedRow(JSONObject row) {
        String mobile = row.getString("mobile");
        try {
            getMobileVerifyCodeWrapper().getPhone(mobile);
            getMobileVerifyCodeWrapper().releasePhone(mobile);

            String cookies = row.getString("cookies");
            CookieStore cookieStore = Requests.toCookieStore(cookies);
            HttpClient httpClient = ensureLogin(cookieStore, row);
            Double cpctQty = jdbcTemplate.queryForObject("select cpctQty from t_cpct_sub_account where mobile = ?", double.class, mobile);
            if (cpctQty < 6) {
                jdbcTemplate.update("update t_cpct_sub_account set status = 'FAILURE', errorMessage = ? where mobile = ?", "无CPCT余额", mobile);
                return false;
            }
            try {
                this.setOthersProps(httpClient, mobile);
            } catch (Exception e) {
                jdbcTemplate.update("update t_cpct_sub_account set status = 'FAILURE', errorMessage = ? where mobile = ?", e.getMessage(), mobile);
                return false;
            }
            JSONObject newRow = queryForRow(jdbcTemplate, "select * from t_cpct_sub_account where mobile = ?", mobile);
            return StringUtils.isNotBlank(newRow.getString("payPass")) && StringUtils.isNotBlank(newRow.getString("ethPublicKey"));

        } catch (Exception e) {
            jdbcTemplate.update("update t_cpct_sub_account set status = 'FAILURE', errorMessage = ? where mobile = ?", e.getMessage(), mobile);
            return false;
        }
    }

}
